/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.situations;

import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.geometry.Vektor;
import robocup.component.math.RCMath;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.UPDATE_MODE;

/**
 * This is our central evaluation function.
 * 
 * All Situations evaluate a number of possible actions each resulting in a new
 * situation. All of the resulting situations are then evaluated and the benefit
 * of each resulting situation is represented as a number between 0..1. As the 
 * fine tuning of the different Situation Modesl is one of the most crucial and 
 * complex problems in robocup we use a central approach for all models. Its 
 * mainly used to decide what to do if the player has the ball.<br>
 * By now most of the values used in this class are set using 'human learning'.
 * However, we plan to find a better setup using machine learning approaches.
 * <br><br>
 * <b>TODO:</b>
 * <br>
 * <ul>
 *  <li>learn the weights used here (offline|online)</li>
 * </ul>
 */
public class SituationEvaluator {

	private static Vektor dummyVektor = new Vektor();

	private static Vektor dummyVektor2 = new Vektor();

  /**
   * the way the new situation is reached (high level action).
   */
	public enum TYPE {
		BALL_INTERCEPTION, 
    DRIBBLING, 
    WAIT_WITH_BALL, 
    PASSING,
    KICKNRUN
	}

	/**
	 * returns the benefit of a situation based on some meta data. by now it is
	 * only used for situations with ball control.
	 * 
	 * @param type -
	 *            the type of situation
	 * @param currentPos -
	 *            the current pos / ball pos
	 * @param nextPos -
	 *            the resulting pos /ball pos
	 * @param security -
	 *            the security in cycles in the resulting situation (time of
	 *            opponent interception)
	 * @param interceptionTime -
	 *            the time of the situation - the current time (used only for ball 
   *            interception)
	 * @param p -
	 *            the player who will have the ball in the resulting situation
	 * @param wm -
	 *            the world model (used only for debug output)
	 * @return a value between 0..1 (1 best)
	 */
	public synchronized static double getValue(TYPE type, Vektor currentPos,
			Vektor nextPos, double security, int interceptionTime, Player p,
			WorldModel wm) {

    //always prefer getting near the goal
		Vektor finalPosition = SConf.getInstance().GOAL_POS_OTHER;

    //the offenseFactBall is used for telling wether the ball moved
    //to a better position
		double offenseFactBall;
    
    //the offenseFactPlayer is used for telling wether the player moves
    //to a better position (i.e. passes which result in the teammate
    //getting nearer to the opponents goal are prefered to passes
    //in the back of an attacking teammate)
		double offenseFactPlayer;    
    
    //the offense weight when in attack (get near opponents goal)
		if (currentPos.y > 30 || nextPos.y > 40) {
			offenseFactBall = currentPos.getDistance(finalPosition)
					- nextPos.getDistance(finalPosition);
			offenseFactPlayer = p.getPosition().getDistance(finalPosition)
					- nextPos.getDistance(finalPosition);
		}     
    //the offense weight when in defense (get away from own goal)
    else if (currentPos.y < -20 || nextPos.y < -10) {
			offenseFactBall = nextPos
					.getDistance(SConf.getInstance().GOAL_POS_OWN)
					- currentPos.getDistance(SConf.getInstance().GOAL_POS_OWN);
			offenseFactPlayer = nextPos
					.getDistance(SConf.getInstance().GOAL_POS_OWN)
					- p.getDistance(SConf.getInstance().GOAL_POS_OWN);
		} 
    //the offense weight when in midfield (go in Y-Dir)
    else {
			offenseFactBall = (nextPos.y - currentPos.y);
			offenseFactPlayer = (nextPos.y - p.getPosition().y);
		}

    //normalize weight
		offenseFactBall = Math.min(offenseFactBall, 30);
		offenseFactBall = Math.max(offenseFactBall, -30);
		offenseFactBall = offenseFactBall / 30.0 * 0.5 + 0.5;

		offenseFactPlayer = Math.min(offenseFactPlayer, 30);
		offenseFactPlayer = Math.max(offenseFactPlayer, -30);
		offenseFactPlayer = offenseFactPlayer / 30.0 * 0.5 + 0.5;

		offenseFactPlayer = Math.pow(offenseFactPlayer, 1.5);

    //if we are near the offside line the offense weight are pushed to
    //get in the back of the opponents defense
		if ((type == TYPE.PASSING || type == TYPE.BALL_INTERCEPTION || type == TYPE.KICKNRUN)
				&& wm.getOffsideY(true) < nextPos.y + 3) {
			Math.pow(offenseFactBall, 0.8);
			Math.pow(offenseFactPlayer, 0.8);
		}

    //how dangerous is the resulting situation
    //usd only for situations in the own field half
		double dangerFact = nextPos.y < -25 ? 0.6 : nextPos.y < 0 ? 0.8 : 1.0;

    //danger depends on the action
		switch (type) {
		case DRIBBLING:
		case WAIT_WITH_BALL:
			dangerFact = Math.pow(dangerFact, 0.5);
			break;
      
    //passing in front of the own goal without getting the bal away from the 
    //goal is punished
		case PASSING:
			if (Math.abs(nextPos.x) < 7) {
				dangerFact /= nextPos
            .getDistance(SConf.getInstance().GOAL_POS_OWN) < 10 ? 10
						: 1;
			}
		}

		dummyVektor.copy(nextPos);
		dummyVektor.subFromThis(p.getPosition());

		dummyVektor2.copy(finalPosition);
		dummyVektor2.subFromThis(p.getPosition());

    //try to avoid passes in the back of a teammate, this way we keep our team
    //moving towards the opponents goal
		double interceptAngleFact = dummyVektor.scalar(dummyVektor2)
				/ (dummyVektor.getLength() * dummyVektor2.getLength());
		if (Double.isNaN(interceptAngleFact)) {
			interceptAngleFact = 1.0;
		}
		interceptAngleFact = interceptAngleFact * 0.2 + 0.8;

    //interceptAngleFact only needed for passing
		if (type == TYPE.DRIBBLING || type == TYPE.WAIT_WITH_BALL) {
			interceptAngleFact = 1.0;
		}

    //don't risk to loose the ball to the outside line
		double outFact = 0.5
				+ Math.min(SConf.getInstance().half_length
						- Math.abs(nextPos.y), 3)
				/ 3
				* Math
						.min(SConf.getInstance().half_width
								- Math.abs(nextPos.x), 3) / 3 * 0.5;

		if (outFact < 0)
			outFact = 0;

		outFact = Math.pow(outFact, (interceptionTime + 10) / 11);

    //security depends on the context, it is more important in the defense
    //and less important in front of the players goal as a too high security
    //level would prevent many successful scoring attempts.
		if (type == TYPE.PASSING) {

      //a pass from a defender to an attacker is prefered most
      //a pass rom an attacker to a defender should be avoided
      //therefore we manipulated the security based on the players' roles
      
      //passing player
      //attacking -> more security
			if (p.getRoleNumber() > 8) {
				security += 0.5;
			}
      //defending -> less security
      else if (p.getRoleNumber() < 6) {
				security -= 1.0;
			}

      //receiver
      //attacking -> more security
			if (wm.getSelfRef().getRoleNumber() > 8) {
				security -= 0.5;
			} 
      //defending -> less security
      else if (wm.getSelfRef().getRoleNumber() < 6) {
				security += 0.5;
			}

			double goalDistOther = nextPos
					.getDistance(SConf.getInstance().GOAL_POS_OTHER);

      //if near the opponents goal risk something ...
			if (goalDistOther < 8) {
				security = 3;
			} else if (goalDistOther < 12) {
				security += 2;
			} 
      //if near the goal or near the offside line
      else if (goalDistOther < 16 || wm.getOffsideY(true) < nextPos.y) {
				security += 1;
			}
		}
    
    //we wait with the ball in standards till secure passes are possible
    //as the opponent can't get near the ball
		if (type == TYPE.WAIT_WITH_BALL
				&& (wm.getPlayMode() == PLAY_MODE.KICK_IN_OWN || wm
						.getPlayMode() == PLAY_MODE.CORNER_KICK_OWN)) {
			if (currentPos.y > 0)
				security -= 2;
		}

    //normalize
		security = Math.max(security, 0);
		security = Math.min(security, 3);

		double secureFact = (1.0 + security) / 4.0;

    //without stamina attacking makes less sense -> waiting or passing should
    //be prefered
		double staminaFact = 1;

		switch (type) {
		case KICKNRUN:
			double distGoal = p.getDistance(SConf.getInstance().GOAL_POS_OTHER);

      //getting in the back o the defense is very interesting and we care
      //less about stamina
			if (p.getPosition().y > wm.getOffsideY(true) - 5) {
				distGoal -= Math.min(distGoal, 10);

				double staminaLeft = p.getStamina() - distGoal * 50;

				staminaFact *= Math.min(1, staminaLeft / 1400.0);
			} 
      //normal kickNrun is of less interest and stamina more important
      else {
				double staminaLevel = 2500;
				if (distGoal < 15) {
					staminaLevel = 1300;
				} else if (distGoal < 25)
					staminaLevel = 1800;

				if (p.getStamina() < staminaLevel) {
					staminaFact = 0.15;
				}
			}
			break;

    //avoid passing to teammates with low stamina
		case PASSING:
			distGoal = p.getDistance(SConf.getInstance().GOAL_POS_OTHER);

			double staminaLevel = 2400;
			if (distGoal < 10) {
				staminaLevel = 0;
			} else if (distGoal < 15) {
				staminaLevel = 800;
			} else if (distGoal < 25)
				staminaLevel = 1300;

			if (p.getStamina() < staminaLevel) {
				staminaFact = 0.5;
			}

			break;
		}

    //avoid passing to teammates whith only old knowledge about their position
		double playerAgeFact = 1.0;

		if (type == TYPE.PASSING) {
			playerAgeFact = Math.pow(0.85, p.getAge(wm.getBodyCycle(),
					UPDATE_MODE.META_BASED_ON_INFO));
		} 
    //if kickNrun avoid to choose directions not seen since a long time
    else if (type == TYPE.KICKNRUN) {
			playerAgeFact = Math.pow(0.9, wm.getAgeOfPoint(nextPos));
		}

		// adapt to game score, be more aggressive when we are loosing and more
    //save when we are winning
		if (wm.getGoalDiff() > 0) {
			offenseFactPlayer = Math.pow(offenseFactPlayer, 1.2);
			offenseFactBall = Math.pow(offenseFactBall, 1.1);
		} else if (wm.getGoalDiff() == 0) {
			offenseFactPlayer = Math.pow(offenseFactPlayer, 1.3);
			offenseFactBall = Math.pow(offenseFactBall, 1.2);
		} else {
			offenseFactPlayer = Math.pow(offenseFactPlayer, 1.4);
			offenseFactBall = Math.pow(offenseFactBall, 1.3);
		}

		// if one side of the field is full of opponents we try to get the ball 
    // to the other side (passing)
		double wingFact = 1.0;

		if (type == TYPE.PASSING && currentPos.getDistance(nextPos) > 0.5) {
			double angleToGo = currentPos.getAngleTo(nextPos);

			switch (wm.getDominatedSide()) {
			case RIGHT:
				if (RCMath.isBetween(angleToGo, -135, -30)) {
					wingFact = 1.2;
				}
				break;

			case LEFT:
				if (RCMath.isBetween(angleToGo, 30.0, -135.0)) {
					wingFact = 1.2;
				}
				break;

			case NONE:
				if (RCMath.isBetween(angleToGo, 30.0, -135.0)
						|| RCMath.isBetween(angleToGo, -135, -30)) {
					wingFact = 1.1;
				}
				break;
			}
		}

    //goalie has some logic of its own
		if (wm.getSelfRef().isGoalie()) {
      
      //don't be offensive ;-)
			offenseFactPlayer = 1.0;

      //try to avoid passing to the center instead prefer wings
			if (type == TYPE.PASSING) {
				double dirAngle = currentPos.getAngleTo(nextPos);
				if (Math.abs(dirAngle) > 100) {
					return -0.12;
				}
				wingFact = 1.0;

			}
			
      //security is more important
			secureFact *= secureFact;
      
      //interceptAngleFact is of no importance
			interceptAngleFact = 1.0;
      
      //the passing to lately seen players is prefered
			playerAgeFact *= playerAgeFact;
		}

    //get the final benefit value
		double value = offenseFactBall * offenseFactPlayer * outFact
				* secureFact * interceptAngleFact * staminaFact * dangerFact
				* playerAgeFact * wingFact;

		return value;
	}
}
